ZYNQ-7000 是 ARM Cortex-A9 + FPGA 架构的 SoC,启动过程由硬件(PS)主导,分阶段完成系统启动和可编程逻辑配置。ZYNQ 7000 SoC的启动是一个多阶段、高度可配置的过程,涉及硬件自动加载和软件控制的复杂交互。本文将详细剖析从电源上电到操作系统加载的完整流程。
PS(ARM)和 PL(FPGA)复位。
读取 BOOT_MODE 引脚状态决定启动介质。
保持 PL 未配置状态。
POR (Power-On Reset) 序列
所有电源轨达到稳定状态(PS_POR_B信号拉高)
内部振荡器启动(典型频率33.33MHz)
时钟分频器设置为默认值
时钟网络配置
x// BootROM初始时钟配置
// 典型初始化值writel(ARM_PLL_CTRL, 0x0001A008); // 设置PLL旁路启动模式由MIO[5:2]引脚决定:
| MIO[5:2] | 启动模式 | 时钟频率 | 数据宽度 |
|---|---|---|---|
| 0000 | JTAG | - | - |
| 0010 | NAND | 33MHz | 8-bit |
| 0011 | NOR | 50MHz | 16-bit |
| 0101 | QSPI | 100MHz | 4-bit |
| 0110 | SD | 50MHz | 4-bit |
注:模式引脚在POR_B上升沿被采样锁定
BootROM 代码是在 Zynq 器件上上电或复位后最先执行的软件。它从地址 0xFFFF_0000 开始执行。 BootROM 对用户不可访问。BootROM代码存放片内ROM当中,所以叫做BootROM。因为ZYNQ内部包含256K RAM 以及128K ROM。所以BootROM代码可以固化在ROM当中,并且在掉电情况下不会丢失。一般情况下,芯片内部的ROM都是Nor Flash。NOR Flash 的特点是芯片内执行(XIP ,eXecute In Place),这样应用程序可以直接在Flash闪存内运行,不必再把代码读到系统RAM中。是固化在芯片中的 ROM,无法修改。
初始化 PS 内部硬件。
从启动介质读取 BOOT.BIN 的头部与 FSBL。
读取启动模式引脚并确定启动设备。
初始化时钟和外设。
从选定的启动设备中读取启动镜像。
解析镜像头并检查其有效性。
将 FSBL 分区加载至 OCM。
将控制权交给 FSBL。
初始化MI0引脚,主要配置MIO引脚的物理特性配置寄存器;重点是将MIO40~MIO45复用为SD0外设所对应的CLK/CMD/DATA引脚。
初始化SD卡外设,驱动SD卡,可以实现sD卡读写操作。
对SD卡读写进行测试。
从SD卡文件系统当中读取BOOT. BIN文件,并对BootROM头进行解析。在BOOT.BIN文件前面有一段头部信息,这个头部信息是按照一-定的格式组组织在一起,在这个头部信息当中就包括了fsbl的加载地址、fsbl的 大小以及fsbl在BOOT. BIN文件中的位置偏移量。BootROM代码能够解析这个头部信息
得到fsbl代码的大小和位置偏移量以及加载地址之后,BootROM代码就会从BOOT.BIN文件中。将fsbl代码拷贝到RAM内存中,并且跳转到fsbl代码的运行地址去启动fsbl。
自此,BootROM就成功启动了FSBL代码了。
在QSPI启动方式下,BootROM代码的运行流程:
初始化MIO引脚,将相关的MIO引脚复用为QSPI外设所需的引脚功能。
初始化QSPI外设,驱动QSPI Flash设备,可以实现QSPI读写操作。
对QSPI进行读写测试。
从QSPI存储介质中读取BOOT. BIN文件,并对Boot ROM头进行解析。
得到fsbl代码的大小和位置偏移量以及加载地址之后,BootROM代码就会从BOOT.BIN文件中。将fsbl代码拷贝到RAM内存中,并且跳转到fsbl代码的运行地址去启动fsbl。
不同于SD卡的文件系统的搜索方式,在QSPI启动方式下,BootROM代码首先会从QSPI的0x000000地址去找BOOT . BIN文件,如果找不到那么就去下一个地址0x008000,如果还找不到他又会跳转到下一个地址0x10000,但是搜索范围不能超出QSPI的前面16MB地址空间。
BOOT.BIN是ZYNQ 7000系列SoC的核心启动镜像文件(启动镜像就是一个包含系统启动所需多个程序或数据的打包文件,通常是一个二进制文件,用于引导系统从上电到操作系统启动的整个过程。在 Zynq 中:启动镜像通常是 BOOT.BIN 文件,BOOT.BIN是整个系统启动的核心文件,必须放在 Zynq 启动设备的起始位置,它被 Zynq 的 ROM 引导代码从 QSPI、SD 卡或 NAND 中读取并执行。),由Bootgen工具根据 .bif 文件打包生成,包含完整的启动链所需的所有组件。例如:
xxxxxxxxxxbifthe_ROM_image:{ [bootloader] fsbl.elf // 第一分区:FSBL,必须第一个 system.bit // 第二分区:FPGA 配置文件(可选) u-boot.elf // 第三分区:第二阶段引导加载器}上述中:
| 分区顺序 | 内容 | 作用 |
|---|---|---|
| 1 | fsbl.elf | 第一阶段引导加载器,初始化硬件和DDR |
| 2 | system.bit | 配置 PL(FPGA 部分) |
| 3 | u-boot.elf | 第二阶段引导,启动 Linux或其他应用程序 |
xxxxxxxxxx┌───────────────────────┐│ Boot Header │ → 固定格式头(256字节)├───────────────────────┤│ Partition Header Table│ → 每个分区描述(20字节/条目)├───────────────────────┤│ FSBL (必选) │ → 第一阶段引导程序├───────────────────────┤│ Bitstream (可选) │ → PL配置数据├───────────────────────┤│ SSBL/应用 (可选) │ → U-Boot/Linux内核,应用程序等├───────────────────────┤│ 认证证书 (可选) │ → RSA签名/AES加密数据└───────────────────────┘
BOOT.BIN头是BOOT.BIN文件前面的一段头部数据,并且这个头部数据是按照一定格式组织在一起的, 并且该头部数据能够被BootROM代码所解析。BootROM 会读取启动镜像中的 Boot Header 来获取关于镜像的信息,例如:
FSBL 的起始地址。
加密密钥的来源。
FSBL 的大小。
FSBL的偏移量
用于验证头部完整性的校验和。
Boot Header 必须存在且格式正确。 否则,BootROM 不会加载 FSBL。
在BOOT.bin文件中从地址0-0x8FF可以分成17个部分,每个部分都有一定的含义。
0x000:中断向量表。
0x020:固定值0xaa995566(小端)。
0x024:固定值0x584c4e58 ASCII: XLNX。
0x028:如果是0xa5c3c5a3或者0x3a5c3c5a为加密的。
0x02C:BootROM头版本号,不用管。
0x030:此参数包含从有效bootrom头开始到fslb/用户代码映像所在位置的字节数,也就是 FSBL/用户代码的地址偏移量。该地址偏移量必须要大于等于0x8C0。
0x034:记录fsbl的长度,用于指导BootROM代码拷贝 fsbl 长度。
0x038:将FSBL拷贝到OCM的什么位置一般为0x0,加载地址,指导BootROM代码拷贝FSBL到RAM的哪个位置。
0x03C:FSBL在OCM中的运行地址一般定义为0x0,运行地址,指导BootROM代码跳转到RAM哪个地址去运行。
0x040:记录FSBL的长度。
0x044:为固定值0x01。
0x048:校验和(将Ox020-0x047之间的数据按32bit长度进行相加,并取反即可!若相加之后的数据大小超过32bit,则取低32bit 数据进行取反)。
0x04C-0x097:fsbl/用户代码自定义,不需要的话可以全部填充为0。
0x098:image header table位置偏移量。
0x09C:partition header table 的所在位置。
0x0A0-0x89F:寄存器初始化的参数。
17.0x8C0:FSBL、用户代码必须要等于或高于此地址。
在0x034地址处可以查看FSBL的长度:
在0x000-0x01F地址处可查看中断向量表的入口地址:
在0x020地址处可查看固定值:
初始PC指针
ARM Cortex-A9从地址0x00000000开始执行
前64KB为BootROM镜像(只读)
关键操作序列:
镜像头部结构:
xxxxxxxxxxtypedef struct { uint32_t width_detection; // 0xAA995566 uint32_t image_offset; // FSBL偏移量 uint32_t image_size; // 最大192KB uint32_t reserved[5]; uint32_t checksum; // 头校验和} boot_header;OCM内存映射:
| 地址范围 | 用途 | 大小 |
|---|---|---|
| 0x0000_0000 | BootROM | 64KB |
| 0x0000_FFFC | 启动状态寄存器 | 4B |
| 0xFFFF_0000 | FSBL加载区域 | 192KB |
初始化DDR控制器
配置MIO引脚复用
设置系统时钟
使能中断控制器
FSBL 是 Zynq 启动流程中的 第一阶段引导程序,由 BootROM 直接加载并执行,主要功能包括:
初始化硬件:配置 DDR 内存、时钟、MIO(多功能 IO)等基础外设。
解析 BOOT.BIN:读取镜像中的分区表(Partition Header Table),识别各分区(如 Bitstream、SSBL、应用程序等)。
加载后续镜像:将第二阶段的程序(如 U-Boot、裸机应用、Linux 内核)拷贝到指定内存地址。
可选加载 PL 配置:如果存在 FPGA Bitstream,FSBL 会将其配置到 PL(Programmable Logic)部分。
场景 1:裸机应用(Standalone)
BOOT.BIN 结构:
xxxxxxxxxxBOOT.BIN = FSBL + Bitstream(可选) + 应用程序(.elf)
流程:
BootROM 加载 FSBL 到 OCM(On-Chip Memory)并执行。
FSBL 初始化 DDR,将应用程序(如 application.elf)从 Flash/QSPI/SD 卡加载到 DDR 中的指定地址(链接脚本定义,如 0x00100000)。
FSBL 跳转到应用程序的入口地址(通常是 _start 或 main)。
场景 2:运行 Linux(通过 U-Boot)
BOOT.BIN 结构:
xxxxxxxxxxBOOT.BIN = FSBL + Bitstream(可选) + U-Boot
流程:
FSBL 加载 U-Boot(SSBL)到 DDR。
U-Boot 进一步加载 Linux 内核(uImage)、设备树(.dtb)、根文件系统(如 rootfs.cpio)到内存。
U-Boot 启动内核,此时 FSBL 已退出,不再参与后续流程。
场景 3:直接加载 Linux(无 U-Boot)
少数情况下,FSBL 可直接加载 Linux 内核(需配置为 kernel.img 格式),但通常推荐使用 U-Boot 作为中介。
无操作系统时:FSBL 直接跳转到应用程序,不返回。
有 U-Boot 时:FSBL 仅负责加载 U-Boot,由 U-Boot 处理后续启动。
比特流加载流程:
通过PCAP接口传输
使用DMA加速(典型吞吐量400Mbps)
状态检查:
xxxxxxxxxx
void load_bitstream(uint32_t* addr, uint32_t size) { writel(PCAP_PROG_B, 0x0); // 断言PROG_B delay(100); writel(PCAP_PROG_B, 0x1); // 释放PROG_B while(size > 0) { uint32_t data = *addr++; writel(PCAP_DATA, data); size -= 4; while(readl(PCAP_STATUS) & 0x4); // 等待DMA就绪 }}| 比特流大小 | 配置时间(100MHz) |
|---|---|
| 1MB | 80ms |
| 5MB | 400ms |
| 10MB | 800ms |
BOOT.BIN文件当中包含了FSBL镜像、u-boot镜像以及bitstream 文件。
BootROM代码需要通过解析BOOT.BIN头部信息去找到FSBL。BootROM代码去启动FSBL。
FSBL代码运行之后,要负责从 BOOT.BIN文件中找到U-Boot镜像和 bitstream文件,然后把 bitstream文件加载到ZYNQ PL端,然后要启动U-Boot。
这里需要涉及到三个数据表:
image headelr table;
partition header table;
image header。
image header table 只有一个,partition header table和 image header是成对出现的。BOOT.BIN 文件中包含了多少个镜像,那么就有多少对partition header table 和 imageheader。
0x00:image header table 的版本号;
ox04:image header的数量;
0x08:第一个Partition Header table的位置偏移量。这里是以word为单位计算的,所以实际的偏移量需要乘上一个4;
0x0C:第一个Image Header的位置偏移量。采用了word 度量单位;
0x10:header authentication 的偏移量。采用了word为度量单位;
0x1C:使用0xFFFFFFFF进行填充,直到整个image header table的大小为64字节。
0x0:下一个limage header 的地址偏移量,如果这里填充为0,则表示这是最后一个image header;
0x4:与之相关联的partition header table 的位置偏移量;
0x8:该地址总是0;
0xC:实际分区计数的值;
0x10-N:记录镜像名称。
varies:用于填充。
0x0:加密分区的数据长度;单位是字计算时必须要乘上4;
0x4:未加密分区的数据长度,如该分区是u-boot,则指示了u-boot的长度,计算同上;
0x8:加密+填充+扩展+身份验证的数据总长度;
0xC:该分区数据的加载地址,指的是该分区数据需要拷贝到内存的什么位置;
0x10:该分区数据的运行地址,指运行该分区代码时需要跳转到那个内存地址;
0x14:该分区数据在BOOT.BIN文件中的位置偏移量,拷贝的时候就是从该地址进行拷贝的;
0x18:属性位;
0x1C: Section计数;
0x20:校验和字段的位置;
0x24:该partition header table所对应的 image header所在位置。以 word字为单位;
0x28:加密相关的字段;
0x2C-0x38:未定义;
0x3C:校验和。
作用:描述整个启动镜像的全局属性。
典型字段:
xxxxxxxxxxtypedef struct { uint32_t magic; // 魔数(如"XNLX") uint32_t version; // 镜像版本 uint32_t partition_num; // 分区数量 uint32_t header_checksum;// 头校验和} ImageHeader;存储位置:镜像文件的起始位置。
作用:索引所有分区的元数据(非分区数据本身)。
内容:
每个分区的 偏移量(offset)、大小(size)、加载地址(load_addr)
分区认证证书的指针
示例:
xxxxxxxxxxtypedef struct { uint32_t partition_offset; // 分区数据在镜像中的偏移 uint32_t partition_size; uint32_t load_address; // 加载到内存的地址 uint32_t attribute_word; // 分区属性(加密/认证等)} ImageHeaderTableEntry;作用:描述单个分区的详细属性。
典型字段:
xxxxxxxxxxtypedef struct { uint32_t exec_address; // 执行入口地址 uint32_t auth_cert_offset; // 认证证书偏移 uint8_t hash[32]; // 分区哈希值 uint8_t reserved[16];} PartitionHeaderTable;关键差异:
每个分区独立拥有一个分区头表
包含分区专属的安全信息(如哈希、证书)
BootROM 读取 Image Header 验证镜像完整性。
FSBL 解析 Image Header Table 定位所有分区。
对每个分区:
通过 Partition Header Table 验证分区合法性(RSA/哈希)。
按 Image Header Table 中的地址加载到内存。
FSBL 是 ZYNQ 平台上电后执行的第一个用户可编程代码,主要负责硬件初始化和加载下一阶段引导程序。其核心架构如下:
main() 函数)xxxxxxxxxxint main(void) { // 1. PS7初始化(MIO/PLL/时钟/DDR) Status = ps7_init(); // 2. 解锁SLCR寄存器 SlcrUnlock(); // 3. 缓存处理 Xil_DCacheFlush(); Xil_DCacheDisable(); // 4. 异常处理注册 RegisterHandlers(); // 5. 外设初始化(根据启动模式) switch(BootModeRegister) { case QSPI_MODE: InitQspi(); case NAND_MODE: InitNand(); case SD_MODE: InitSD(); // ... } // 6. 加载启动镜像 HandoffAddress = LoadBootImage(); // 7. 移交控制权 FsblHandoff(HandoffAddress);}LoadBootImage())xxxxxxxxxxu32 LoadBootImage(void) { // 1. 获取启动状态和多启动寄存器值 RebootStatusRegister = Xil_In32(REBOOT_STATUS_REG); MultiBootReg = Xil_In32(XDCFG_MULTIBOOT_ADDR_OFFSET); // 2. 获取镜像起始地址 ImageStartAddress = ComputeImageStart(); // 3. 获取分区头信息 Status = GetPartitionHeaderInfo(ImageStartAddress); // 4. 分区处理循环 while (PartitionNum < PartitionCount) { // - 验证分区头 Status = ValidateHeader(HeaderPtr); // - 加载分区数据 Status = PartitionMove(ImageStartAddress, HeaderPtr); // - 安全验证(RSA/校验和) if (SignedPartitionFlag) { Status = AuthenticatePartition(...); } // - PL比特流处理 if (PLPartitionFlag) { Status = PcapLoadPartition(...); } PartitionNum++; } return ExecAddress; // 返回移交地址}PartHeader)xxxxxxxxxxtypedef struct { u32 image_word_len; // 镜像中的分区长度(字) u32 data_word_len; // 实际数据长度(字) u32 partition_word_len;// 分区总长度(字) u32 load_addr; // 加载地址(DDR) u32 exec_addr; // 执行地址 u32 partition_offset; // 分区在镜像中的偏移 u32 attribute; // 属性字(加密/签名等) u32 checksum; // 分区头校验和} PartHeader;xxxxxxxxxx// PL比特流分区// PS代码分区// RSA签名存在// 分区所有者xxxxxxxxxx// RSA签名验证Status AuthenticatePartition(u8 *PartitionStart, u32 Length) { // 1. 验证签名 if (XSecure_RsaVerify(...) != XST_SUCCESS) { return XST_FAILURE; } // 2. 检查哈希 if (ValidateHash(...) != XST_SUCCESS) { return XST_FAILURE; } return XST_SUCCESS;}
// 加密分区解密Status DecryptPartition(u32 Addr, u32 DataLen, u32 ImgLen) { // 使用AES引擎解密 XDcfg_AesDecrypt(...);}xxxxxxxxxx0x00000000 - 0x0003FFFF: BootROM + FSBL0x00100000 - 0x001FFFFF: U-Boot0x00200000 - 0x002FFFFF: 设备树0x03000000 - 0x04000000: Linux内核0x08000000 - : 根文件系统xxxxxxxxxxsetenv bootargs 'console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlyprintk'setenv loadaddr 0x03000000setenv fdtaddr 0x00200000bootm ${loadaddr} - ${fdtaddr}使用BBRAM或eFUSE存储密钥
CBC模式,256位密钥
每个分区独立IV向量
xxxxxxxxxxvoid set_multiboot(uint32_t offset) { uint32_t val = (offset >> 17) & 0x3FF; // 转换为128KB块 writel(MULTIBOOT_REG, val | 0x1); // 使能位}xxxxxxxxxx0x000000: Golden Image (备份)0x100000: 主镜像v1.00x200000: 主镜像v2.00x300000: 诊断镜像| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 卡在BootROM | 启动设备检测失败 | 检查MIO引脚配置 |
| FSBL加载失败 | OCM空间不足 | 优化FSBL大小<192KB |
| PL配置超时 | PCAP接口时钟未使能 | 检查SLCR寄存器配置 |
启用QSPI的DMA传输(设置QSPI_CR[3]=1)
使用XIP(Execute-In-Place)模式运行FSBL
压缩比特流(通过BITSTREAM.CONFIG.COMPRESS)
xxxxxxxxxx0xF8000000 - 系统级控制寄存器0xF8000B00 - MIO引脚控制0xF8000D00 - 时钟控制xxxxxxxxxx0xF8007000 - PCAP控制0xF8007030 - 安全配置0xF80070A0 - 多镜像控制UG585: Zynq-7000 Technical Reference Manual
UG821: Zynq-7000 Software Developers Guide
UG873: Embedded Design Tutorial
XAPP1175: Secure Boot of Zynq-7000 SoC